/**
 * 
 */
package uk.ac.ebi.curtain.utils.io;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.utils.concurrent.UtilsCollect;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;

/**
 * @author mhaimel
 *
 */
public class MultipleFileHandler<O extends Closeable,T extends Object> implements Closeable{
	private Log log = LogFactory.getLog(this.getClass());
	
	private Map<T, O> outputMap = UtilsCollect.newConcurrentMap();
	private Map<T, File> fileMap = UtilsCollect.newConcurrentMap();
	private AtomicReference<FileAccess<O>> access = new AtomicReference<FileAccess<O>>();
	
	public MultipleFileHandler(FileAccess<O> access) {
		this.access.set(access);
	}
	
	public void addFile(File newFile, T id){
		File currFile = fileMap.get(id);
		if(null != currFile && !currFile.equals(newFile)){
			throw new IllegalArgumentException(
					"File already exist for identifier " + id+": " + currFile +" <> " + newFile);
		}
		fileMap.put(id, newFile);
	}
	
	private File getFile(T id){
		return fileMap.get(id);
	}
	
	public O getChannel(T id) throws IOException{
		O out = getStoredChannel(id);
		if(null == out){
			out = _openChannel(id);
		}
		return out;
	}
	
	private Log getLog() {
		return log;
	}
	
	private O _openChannel(T id) throws IOException {
		O out = getStoredChannel(id);
		File file = getFile(id);
		if(null == out){
			if(null == file){
				throw new IOException(
						"Not able to open output - no file found for Id " + id);
			}
			out = openOutput(file);
			outputMap.put(id, out);
		} else {
			getLog().warn("Channel already open for " + id + " -> " + file);
		}
		return out;
	}

	private FileAccess<O> getAccess(){
		return this.access.get();
	}
	
	private O openOutput(File file) throws IOException {
		O openFile = getAccess().openFile(file);
		if(null == openFile){
			throw new IOException(
				"Opening of file failed: " + file);
		}
		return openFile;
	}

	private O getStoredChannel(T id) {
		return outputMap.get(id);
	}
	
	public void openAll() throws IOException{
		for(T id : this.fileMap.keySet()){
			_openChannel(id);
		}		
	}

	public void closeAll(){
		Set<T> keySet = new HashSet<T>(this.outputMap.keySet());
		for(T id : keySet){
			close(id);
		}
	}
	
	public void close(T id){
		O out = this.outputMap.remove(id);
		FileIO.closeQuietly(out);
	}
	
	public static interface FileAccess<O extends Closeable>{
		public O openFile(File file) throws IOException;
	}

	@Override
	public void close() throws IOException {
		closeAll();
	}
	
	private class ClosableWrapper<O> implements Closeable{
		private AtomicReference<O> ref = new AtomicReference<O>();
		private AtomicInteger id = new AtomicInteger();
		
		public ClosableWrapper(Integer id) {
			this.id.set(id);
		}

		@Override
		public void close() throws IOException {
			// TODO Auto-generated method stub
		}
		
//		private O getReference(){
//			return 
//		}
		
	}
}
